//-----------------------------------------------------------------------------
// Filename: UACInviteTransaction.cs
//
// Description: SIP Transaction that implements UAC (Sser Agent Client) functionality for
// an INVITE transaction.
// 
// History:
// 21 Nov 2006	Aaron Clauson	Created.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Text.RegularExpressions;
using BlueFace.VoIP.Authentication;
using BlueFace.Sys;
using BlueFace.Sys.Net;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace BlueFace.VoIP.Net.SIP
{
	/// <summary>
    /// SIP transaction that initiates a call to a SIP User Agent Server. This transaction processes outgoing calls SENT by the application.
	/// </summary>
    public class UACInviteTransaction : SIPTransaction
    {
        public event SIPTransactionResponseReceivedDelegate UACInviteTransactionInformationResponseReceived;
        public event SIPTransactionResponseReceivedDelegate UACInviteTransactionFinalResponseReceived;
        public event SIPTransactionTimedOutDelegate UACInviteTransactionTimedOut;
        
        public SIPCDR CDR;

        public UACInviteTransaction(SIPTransport sipTransport, SIPRequest sipRequest, IPEndPoint dstEndPoint, IPEndPoint sendFromEndPoint, SIPProtocolsEnum protocol)
            : base(sipTransport, sipRequest, dstEndPoint, sendFromEndPoint, protocol)
		{
            TransactionType = SIPTransactionTypesEnum.Invite;
            m_localTag = sipRequest.Header.From.FromTag;
            CDR = new SIPCDR(SIPCallDirection.Out, sipRequest.URI, sipRequest.Header.From, sipRequest.Header.CallId, sendFromEndPoint, dstEndPoint);

            TransactionFinalResponseReceived += new SIPTransactionResponseReceivedDelegate(UACInviteTransaction_TransactionFinalResponseReceived);
            TransactionInformationResponseReceived += new SIPTransactionResponseReceivedDelegate(UACInviteTransaction_TransactionInformationResponseReceived);
            TransactionTimedOut += new SIPTransactionTimedOutDelegate(UACInviteTransaction_TransactionTimedOut);
            TransactionRequestReceived += new SIPTransactionRequestReceivedDelegate(UACInviteTransaction_TransactionRequestReceived);
            TransactionRemoved += new SIPTransactionRemovedDelegate(UACInviteTransaction_TransactionRemoved);
        }

        private void UACInviteTransaction_TransactionRemoved(SIPTransaction transaction)
        {
            // Remove event handlers.
            UACInviteTransactionInformationResponseReceived = null;
            UACInviteTransactionFinalResponseReceived = null;
            UACInviteTransactionTimedOut = null;
            CDR = null;
        }

        public void SendInviteRequest(IPEndPoint dstEndPoint, SIPRequest inviteRequest)
        {
            this.RemoteEndPoint = dstEndPoint;
            base.SendReliableRequest();
        }

        private void UACInviteTransaction_TransactionRequestReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPRequest sipRequest)
        {
            logger.Warn("UACInviteTransaction received unexpected request, " + sipRequest.Method + " from " + IPSocket.GetSocketString(remoteEndPoint) + ", ignoring.");
        }

        private void UACInviteTransaction_TransactionTimedOut(SIPTransaction sipTransaction)
        {
            if (UACInviteTransactionTimedOut != null)
            {
                UACInviteTransactionTimedOut(sipTransaction);
            }
        }

        private void UACInviteTransaction_TransactionInformationResponseReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
        {
            try
            {
                if (CDR != null)
                {
                    CDR.Progress(sipResponse.Status, sipResponse.ReasonPhrase);
                }

                if (UACInviteTransactionInformationResponseReceived != null)
                {
                    UACInviteTransactionInformationResponseReceived(localEndPoint, remoteEndPoint, sipTransaction, sipResponse);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception UACInviteTransaction_TransactionInformationResponseReceived. " + excp.Message);
            }
        }

        private void UACInviteTransaction_TransactionFinalResponseReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
        {
            try
            {
                if (CDR != null)
                {
                    CDR.Answered(sipResponse.Status, sipResponse.ReasonPhrase);
                }

                // BranchId for 2xx responses needs to be a new one, non-2xx final responses use same one as original request.
                SIPRequest ackRequest = null;
                if (sipResponse.StatusCode >= 200 && sipResponse.StatusCode < 299)
                {
                    if (sipResponse.Header.To != null)
                    {
                        m_remoteTag = sipResponse.Header.To.ToTag;
                        //SIPTransaction.RemoveTransaction(TransactionId);
                    }

                    SIPURI ackURI = m_transactionRequest.URI;
                    if (sipResponse.Header.Contact != null && sipResponse.Header.Contact.Count > 0)
                    {
                        ackURI = sipResponse.Header.Contact[0].ContactURI;
                    }

                    ackRequest = GetAckRequest(sipResponse, ackURI, SendFromEndPoint, CallProperties.CreateBranchId());
                }
                else
                {
                    ackRequest = GetAckRequest(sipResponse, m_transactionRequest.URI, SendFromEndPoint, sipResponse.Header.Via.TopViaHeader.Branch);
                }

                SendAckRequest(ackRequest);

                if (UACInviteTransactionFinalResponseReceived != null)
                {
                    UACInviteTransactionFinalResponseReceived(localEndPoint, remoteEndPoint, sipTransaction, sipResponse);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception UACInviteTransaction_TransactionFinalResponseReceived. " + excp.Message);
            }
        }

        /// <summary>
        /// From RFC 3261 Chapter 17.1.1.3 - ACK for non-2xx final responses
        /// 
        /// IMPORTANT:
        /// an ACK for a non-2xx response will also have the same branch ID as the INVITE whose response it acknowledges.
        /// 
        /// The ACK request constructed by the client transaction MUST contain
        /// values for the Call-ID, From, and Request-URI that are equal to the
        /// values of those header fields in the request passed to the transport
        /// by the client transaction (call this the "original request").  The To
        /// header field in the ACK MUST equal the To header field in the
        /// response being acknowledged, and therefore will usually differ from
        /// the To header field in the original request by the addition of the
        /// tag parameter.  The ACK MUST contain a single Via header field, and
        /// this MUST be equal to the top Via header field of the original
        /// request.  The CSeq header field in the ACK MUST contain the same
        /// value for the sequence number as was present in the original request,
        /// but the method parameter MUST be equal to "ACK".
        ///
        /// If the INVITE request whose response is being acknowledged had Route
        /// header fields, those header fields MUST appear in the ACK.  This is
        /// to ensure that the ACK can be routed properly through any downstream
        /// stateless proxies.
        /// 
        /// From RFC 3261 Chapter 13.2.2.4 - ACK for 2xx final responses
        /// 
        /// IMPORTANT:
        /// an ACK for a 2xx final response is a new transaction and has a new branch ID.
        /// 
        /// The UAC core MUST generate an ACK request for each 2xx received from
        /// the transaction layer.  The header fields of the ACK are constructed
        /// in the same way as for any request sent within a dialog (see Section
        /// 12) with the exception of the CSeq and the header fields related to
        /// authentication.  The sequence number of the CSeq header field MUST be
        /// the same as the INVITE being acknowledged, but the CSeq method MUST
        /// be ACK.  The ACK MUST contain the same credentials as the INVITE.  If
        /// the 2xx contains an offer (based on the rules above), the ACK MUST
        /// carry an answer in its body.  If the offer in the 2xx response is not
        /// acceptable, the UAC core MUST generate a valid answer in the ACK and
        /// then send a BYE immediately.
        /// </summary>
        private SIPRequest GetAckRequest(SIPResponse sipResponse, SIPURI ackURI, IPEndPoint localEndPoint, string branchId)
        {
            SIPRequest ackRequest = new SIPRequest(SIPMethodsEnum.ACK, ackURI.ToString());

            SIPHeader header = new SIPHeader(TransactionRequest.Header.From, sipResponse.Header.To, sipResponse.Header.CSeq, sipResponse.Header.CallId);
            header.CSeqMethod = SIPMethodsEnum.ACK;

            if (sipResponse.Header.RecordRoutes != null)
            {
                header.Routes = sipResponse.Header.RecordRoutes.Reversed();
            }

            ackRequest.Header = header;

            SIPViaHeader viaHeader = new SIPViaHeader(localEndPoint.Address.ToString(), localEndPoint.Port, branchId, base.Protocol);
            ackRequest.Header.Via.PushViaHeader(viaHeader);

            return ackRequest;
        }

        private void SendAckRequest(SIPRequest ackRequest)
        {
            m_ackRequest = ackRequest;
            base.SendRequest(ackRequest);
        }

        public void CancelCall()
        {
            base.Cancel();
        }
	}
}
